﻿using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace PuzzleDiamonds
{
    public enum GameState
    {
        gsInit,
        gsPlay,
        gsGameOver,
        gsEnd
    };

    public class PuzzleDiamondsGame
    {
        private enum DiamondColors
        {
            dcNone,
            dcYellow,
            dcRed,
            dcGreen,
            dcBlue,
            dcGray
        };

        private enum DiamodStatus
        {
            dsStand,
            dsMove
        };

        private enum GridDirection 
        { 
            gdRight, 
            gdDown, 
            gdLeft, 
            gdUp 
        };
       
        private class Diamond
        {
            private DiamondColors m_diamondColor;
            private SceneSprite m_diamondSprite;
            private DiamodStatus m_diamondStatus;
            private Point m_standPosition;

            public DiamondColors DiamondColor
            {
                get { return m_diamondColor; }
                set { m_diamondColor = value; }
            }

            public SceneSprite DiamondSprite
            {
                get { return m_diamondSprite; }
            }

            public DiamodStatus DiamondStatus
            {
                get { return m_diamondStatus; }
                set
                {
                    // let diamond falls/stand...
                    m_diamondStatus = value;
                    if ( m_diamondStatus == DiamodStatus.dsMove)
                        m_diamondSprite.Speed = 0.5;
                    else
                        m_diamondSprite.Speed = 0.0;
                }
            }

            public Point StandPosition
            {
                get { return m_standPosition; }
                set { m_standPosition = value; }
            }

            public Diamond(DiamondColors pColor, Point pPosition)
            {
                m_diamondColor = pColor;
                m_diamondStatus = DiamodStatus.dsStand;
                m_standPosition = pPosition;

                switch (m_diamondColor)
                {
                    case DiamondColors.dcRed:
                        {
                            m_diamondSprite = new SceneSprite("images/red_diamond.png", pPosition, new Point(0.0, 1.0), 0.0);
                            break;
                        }

                    case DiamondColors.dcGreen:
                        {
                            m_diamondSprite = new SceneSprite("images/green_diamond.png", pPosition, new Point(0.0, 1.0), 0.0);
                            break;
                        }

                    case DiamondColors.dcBlue:
                        {
                            m_diamondSprite = new SceneSprite("images/blue_diamond.png", pPosition, new Point(0.0, 1.0), 0.0);
                            break;
                        }

                    case DiamondColors.dcYellow:
                        {
                            m_diamondSprite = new SceneSprite("images/yellow_diamond.png", pPosition, new Point(0.0, 1.0), 0.0);
                            break;
                        }

                    case DiamondColors.dcGray:
                        {
                            m_diamondSprite = new SceneSprite("images/gray_diamond.png", pPosition, new Point(0.0, 1.0), 0.0);
                            break;
                        }
                }
            }
        }
       
        private class Explosion
        {
            private SceneExplodeParticleEmitter m_explodeParticleEmitter;
            private Point m_vPosition;

            public bool Active
            {
                get { return m_explodeParticleEmitter.Active; }
            }

            public Explosion(Scene pScene, DiamondColors pColor, Point pPosition)
            {
                m_vPosition = pPosition;

                switch(pColor)
                {
                    case DiamondColors.dcRed:
                        {
                            m_explodeParticleEmitter = new SceneExplodeParticleEmitter("images/red_spot.png", m_vPosition, new Point(0.0, 0.0), 0.0, pScene, 50, 50, 15.0, 2.0);
                            break;
                        }

                    case DiamondColors.dcGreen:
                        {
                            m_explodeParticleEmitter = new SceneExplodeParticleEmitter("images/green_spot.png", m_vPosition, new Point(0.0, 0.0), 0.0, pScene, 50, 50, 15.0, 2.0);
                            break;
                        }

                    case DiamondColors.dcBlue:
                        {
                            m_explodeParticleEmitter = new SceneExplodeParticleEmitter("images/blue_spot.png", m_vPosition, new Point(0.0, 0.0), 0.0, pScene, 50, 50, 15.0, 2.0);
                            break;
                        }

                    case DiamondColors.dcYellow:
                        {
                            m_explodeParticleEmitter = new SceneExplodeParticleEmitter("images/yellow_spot.png", m_vPosition, new Point(0.0, 0.0), 0.0, pScene, 50, 50, 15.0, 2.0);
                            break;
                        }
                }

                pScene.AddObject(m_explodeParticleEmitter);
            }

            public void Update(double pTick)
            {
                m_explodeParticleEmitter.Update(pTick);
            }

        }

        private const int DIAMOND_SIZE = 32;
        private const double DIAMOND_MOVE_SPEED = 8.0;

        private int m_diamondsGridWidth;
        private int m_diamondsGridHeight;
        private double m_diamondsGridOffsetX;
        private double m_diamondsGridOffsetY;
        private Scene m_gameScene;
        private GameState m_gameState;
        private Diamond[,] m_damondsGrid;
        private int m_gameTick;
        private int m_gameScore;
        private bool m_bIsUpdatingGrid;
        private SceneSprite m_gameOverSprite;
        private SceneSprite m_finishSprite;
        private Collection<Explosion> m_explosionList = new Collection<Explosion>();

        public GameState State
        {
            get { return m_gameState; }
        }

        public PuzzleDiamondsGame(Scene pGameScene, int pDiamondsGridWidth, int pDiamondsGridHeight)
        {
            m_gameScene = pGameScene;
            m_gameState = GameState.gsInit;

            m_diamondsGridWidth = pDiamondsGridWidth;
            m_diamondsGridHeight = pDiamondsGridHeight;

            m_gameOverSprite = new SceneSprite("images/game_over.png", new Point(200.0, 180.0), new Point(0.0, 0.0), 0.0);
            m_finishSprite = new SceneSprite("images/finish.png", new Point(200.0, 180.0), new Point(0.0, 0.0), 0.0);
        }

        private void InitDiamondsGrid(Scene pGameScene)
        {
            m_damondsGrid = new Diamond[m_diamondsGridWidth, m_diamondsGridHeight];

            m_diamondsGridOffsetX = (int)(m_gameScene.SceneCanvas.Width - (m_diamondsGridWidth * DIAMOND_SIZE)) / 2;
            m_diamondsGridOffsetY = (int)(m_gameScene.SceneCanvas.Height - (m_diamondsGridHeight * DIAMOND_SIZE)) / 2;
            Random rnd = new Random();

            for (int x = 0; x < m_diamondsGridWidth; x++)
            {
                for (int y = 0; y < m_diamondsGridHeight; y++)
                {
                    DiamondColors color = (DiamondColors)rnd.Next((int)DiamondColors.dcYellow, (int)DiamondColors.dcBlue + 1);
                    Point position = new Point(m_diamondsGridOffsetX + x * DIAMOND_SIZE, m_diamondsGridOffsetY + y * DIAMOND_SIZE);

                    m_damondsGrid[x, y] = new Diamond(color, position);
                    pGameScene.AddObject(m_damondsGrid[x, y].DiamondSprite);
                }
            }
        }

        private int CleanDiamondGrid(int pGridX, int pGridY, DiamondColors pDiamondColor, GridDirection pGridDirection)
        {
            int lookX = 0;
            int lookY = 0;
            
            switch (pGridDirection)
            {
                case GridDirection.gdRight:
                    {
                        lookX = pGridX + 1;
                        lookY = pGridY;
                        break;
                    }

                case GridDirection.gdDown:
                    {
                        lookX = pGridX;
                        lookY = pGridY + 1;
                        break;
                    }

                case GridDirection.gdLeft:
                    {
                        lookX = pGridX - 1;
                        lookY = pGridY;
                        break;
                    }

                case GridDirection.gdUp:
                    {
                        lookX = pGridX;
                        lookY = pGridY - 1;
                        break;
                    }
            }

            int nDiamondCount = 0;
            if ((lookX >= 0) && (lookX < m_diamondsGridWidth) && (lookY >= 0) && (lookY < m_diamondsGridHeight))
            {
                if (m_damondsGrid[lookX, lookY].DiamondColor == pDiamondColor)
                {
                    // replace colored diamond with gray diamond...
                    Point currentDiamondPos = m_damondsGrid[lookX, lookY].DiamondSprite.Position;
                    
                    m_gameScene.RemoveObject(m_damondsGrid[lookX, lookY].DiamondSprite);                    
                    m_damondsGrid[lookX, lookY] = new Diamond(DiamondColors.dcGray, currentDiamondPos);
                    m_gameScene.AddObject(m_damondsGrid[lookX, lookY].DiamondSprite);

                    nDiamondCount++;

                    nDiamondCount += CleanDiamondGrid(lookX, lookY, pDiamondColor, GridDirection.gdRight);
                    nDiamondCount += CleanDiamondGrid(lookX, lookY, pDiamondColor, GridDirection.gdDown);
                    nDiamondCount += CleanDiamondGrid(lookX, lookY, pDiamondColor, GridDirection.gdLeft);
                    nDiamondCount += CleanDiamondGrid(lookX, lookY, pDiamondColor, GridDirection.gdUp);
                }
            }

            return nDiamondCount;
        }

        private int RemoveGrayDiamonds()
        {
            // remove all grayed diamonds...
            int nCount = 0;
            for (int x = 0; x < m_diamondsGridWidth; x++)
            {
                for (int y = 0; y < m_diamondsGridHeight; y++)
                {
                    if (m_damondsGrid[x, y].DiamondColor == DiamondColors.dcGray)
                    {
                        m_gameScene.RemoveObject(m_damondsGrid[x, y].DiamondSprite);
                        m_damondsGrid[x, y].DiamondColor = DiamondColors.dcNone;
                        nCount++;
                    }
                }
            }

            return nCount;
        }

        private void UpdateDiamondGrid()
        {            
            // let start fall animation for other diamonds...       
            m_bIsUpdatingGrid = false;
            for (int x = 0; x < m_diamondsGridWidth; x++)
            {
                for (int y = 0; y < m_diamondsGridHeight - 1; y++)
                {
                    if ((m_damondsGrid[x, y].DiamondColor != DiamondColors.dcNone) && 
                        (m_damondsGrid[x, y + 1].DiamondColor == DiamondColors.dcNone))
                    {
                        Diamond movedDiamond = m_damondsGrid[x, y];

                        m_bIsUpdatingGrid = true;
                        movedDiamond.DiamondSprite.Speed = DIAMOND_MOVE_SPEED;
                        movedDiamond.DiamondSprite.Direction = new Point(0.0, 1.0);

                        // stop diamond movement
                        if ((int)(movedDiamond.DiamondSprite.Position.Y - movedDiamond.StandPosition.Y) >= DIAMOND_SIZE)
                        {
                            // stop moving dimonds..
                            movedDiamond.DiamondSprite.Position = new Point(movedDiamond.StandPosition.X, movedDiamond.StandPosition.Y + DIAMOND_SIZE);
                            movedDiamond.StandPosition = movedDiamond.DiamondSprite.Position;
                            movedDiamond.DiamondSprite.Speed = 0.0;

                            // put diamond in the new position...
                            m_damondsGrid[x, y] = m_damondsGrid[x, y + 1];
                            m_damondsGrid[x, y + 1] = movedDiamond;
                        }
                    }
                }
            }

            if (!m_bIsUpdatingGrid)
            {
                for (int x = m_diamondsGridWidth - 2; x >= 0; x--)
                {
                    bool bCompact = true;
                    for (int y = 0; y < m_diamondsGridHeight; y++)
                    {
                        if (m_damondsGrid[x, y].DiamondColor != DiamondColors.dcNone)
                        {
                            bCompact = false;
                            break;
                        }
                    }

                    if (bCompact)
                    {                       
                        for (int y = 0; y < m_diamondsGridHeight; y++)
                        {
                            if (m_damondsGrid[x + 1, y].DiamondColor != DiamondColors.dcNone)
                            {
                                m_bIsUpdatingGrid = true;

                                Diamond movedDiamond = m_damondsGrid[x + 1, y];

                                movedDiamond.DiamondSprite.Speed = DIAMOND_MOVE_SPEED;
                                movedDiamond.DiamondSprite.Direction = new Point(-1.0, 0.0);

                                if ((int)(movedDiamond.StandPosition.X - movedDiamond.DiamondSprite.Position.X) >= DIAMOND_SIZE)
                                {
                                    // stop moving dimonds..
                                    movedDiamond.DiamondSprite.Position = new Point(movedDiamond.StandPosition.X - DIAMOND_SIZE, movedDiamond.StandPosition.Y);
                                    movedDiamond.StandPosition = movedDiamond.DiamondSprite.Position;
                                    movedDiamond.DiamondSprite.Speed = 0.0;

                                    // put diamond in the new position...
                                    m_damondsGrid[x + 1, y] = m_damondsGrid[x, y];
                                    m_damondsGrid[x, y] = movedDiamond;
                                }
                            }
                        }
                    }
                }
            }
        }

        private void InsertDiamonds()
        {
            Random rnd = new Random();

            for (int x = 0; x < m_diamondsGridWidth; x++)
            {
                int nDiamondCount = 0;
                for (int y = 0; y < m_diamondsGridHeight; y++)
                {
                    if (m_damondsGrid[x, y].DiamondColor == DiamondColors.dcNone)
                        nDiamondCount++;
                }

                if (nDiamondCount > 4)
                {
                    DiamondColors color = (DiamondColors)rnd.Next((int)DiamondColors.dcYellow, (int)DiamondColors.dcBlue + 1);
                    Point position = new Point(m_diamondsGridOffsetX + x * DIAMOND_SIZE, m_diamondsGridOffsetY);

                    m_damondsGrid[x, 0] = new Diamond(color, position);
                    m_gameScene.AddObject(m_damondsGrid[x, 0].DiamondSprite);
                }
            }

        }

        private bool IsAvailableMove()
        {
            for (int x = 0; x < m_diamondsGridWidth; x++)
            {
                for (int y = 0; y < m_diamondsGridHeight; y++)
                {
                    DiamondColors currDiamondColor = m_damondsGrid[x, y].DiamondColor;

                    if (currDiamondColor != DiamondColors.dcNone)
                    {
                        bool bIsMoveAvailable = false;
                        if (x > 0)
                            bIsMoveAvailable |= (m_damondsGrid[x - 1, y].DiamondColor == currDiamondColor);
                        if (x < (m_diamondsGridWidth - 1))
                            bIsMoveAvailable |= (m_damondsGrid[x + 1, y].DiamondColor == currDiamondColor);
                        if (y > 0)
                            bIsMoveAvailable |= (m_damondsGrid[x, y - 1].DiamondColor == currDiamondColor);
                        if (y < (m_diamondsGridHeight - 1))
                            bIsMoveAvailable |= (m_damondsGrid[x, y + 1].DiamondColor == currDiamondColor);

                        if (bIsMoveAvailable)
                            return true;
                    }
                }
            }
            return false;
        }

        private bool IsAllGridClean()
        {
            for (int x = 0; x < m_diamondsGridWidth; x++)
            {
                for (int y = 0; y < m_diamondsGridHeight; y++)
                {
                    if (m_damondsGrid[x, y].DiamondColor != DiamondColors.dcNone)
                        return false;
                }
            }
            return true;
        }

        private void RemoveSpriteObjectFromScene()
        {
            for (int x = 0; x < m_diamondsGridWidth; x++)
            {
                for (int y = 0; y < m_diamondsGridHeight; y++)
                {
                    if (m_damondsGrid[x, y].DiamondColor != DiamondColors.dcNone)
                        m_gameScene.RemoveObject(m_damondsGrid[x, y].DiamondSprite);
                }
            }
            if (m_gameScene.ExistObject(m_finishSprite))
                m_gameScene.RemoveObject(m_finishSprite);

            if (m_gameScene.ExistObject(m_gameOverSprite))
                m_gameScene.RemoveObject(m_gameOverSprite);
        }

        public void Reinit()
        {
            m_gameState = GameState.gsInit;
        }

        public void OnMouseClick(double pMouseX, double pMouseY)
        {
            switch (m_gameState)
            {
                case GameState.gsPlay:
                    {
                        if (m_bIsUpdatingGrid)
                            return;

                        SoundManager.Instance().PlaySound("clickSound");

                        int diamondGridX;
                        int diamondGridY;

                        diamondGridX = (int)((pMouseX - m_diamondsGridOffsetX) / DIAMOND_SIZE);
                        diamondGridY = (int)((pMouseY - m_diamondsGridOffsetY) / DIAMOND_SIZE);

                        if ((diamondGridX >= 0) && (diamondGridX < m_diamondsGridWidth) && (diamondGridY >= 0) && (diamondGridY < m_diamondsGridHeight))
                        {
                            Diamond selectedDiamond = m_damondsGrid[diamondGridX, diamondGridY];

                            if ((selectedDiamond.DiamondColor != DiamondColors.dcNone) && (selectedDiamond.DiamondColor != DiamondColors.dcGray))
                            {
                                // cleanup diamond grid...
                                int nDiamondCount = 0;

                                nDiamondCount += CleanDiamondGrid(diamondGridX, diamondGridY, selectedDiamond.DiamondColor, GridDirection.gdRight);
                                nDiamondCount += CleanDiamondGrid(diamondGridX, diamondGridY, selectedDiamond.DiamondColor, GridDirection.gdDown);
                                nDiamondCount += CleanDiamondGrid(diamondGridX, diamondGridY, selectedDiamond.DiamondColor, GridDirection.gdLeft);
                                nDiamondCount += CleanDiamondGrid(diamondGridX, diamondGridY, selectedDiamond.DiamondColor, GridDirection.gdUp);

                                // spawn explosion...
                                if (nDiamondCount > 0)
                                    m_explosionList.Add(new Explosion(m_gameScene, selectedDiamond.DiamondColor, new Point(pMouseX, pMouseY)));
                            }
                        }
                        break;
                    }

                case GameState.gsGameOver:
                    {
                        if (IsAllGridClean())
                            m_gameState = GameState.gsInit;
                        else
                            m_gameState = GameState.gsEnd; 
                        break;
                    }
            }
        }

        public void Update(double pTick)
        {
            switch (m_gameState)
            {
                case GameState.gsInit:
                    {
                        try
                        {
                            TextBlock txtGameOver = (TextBlock)m_gameScene.SceneCanvas.FindName("txtGameOver");
                            txtGameOver.Visibility = Visibility.Collapsed;

                            // initialize diamonds grid...
                            InitDiamondsGrid(m_gameScene);

                            m_gameState = GameState.gsPlay;
                            m_gameTick = 0;
                            m_gameScore = 0;

                            if (m_gameScene.ExistObject(m_finishSprite))
                                m_gameScene.RemoveObject(m_finishSprite);

                            if (m_gameScene.ExistObject(m_gameOverSprite))
                                m_gameScene.RemoveObject(m_gameOverSprite);
                        }
                        catch (Exception e)
                        {
                            TextBlock txtScore = (TextBlock)m_gameScene.SceneCanvas.FindName("txtScore");
                            txtScore.Text = "Exception: " + e.Message;
                        }
                        break;
                    }

                case GameState.gsPlay:
                    {
                        try
                        {
                            // check diamonds for move...
                            m_gameTick++;
                            
                            //if ((m_gameTick % 100) == 0)
                            //{
                            //    if (!m_bIsUpdatingGrid)
                            //        InsertDiamonds();
                            //}

                            if ((m_gameTick % 20) == 0)
                            {
                                m_gameScore += (RemoveGrayDiamonds() * 10);
                                //m_gameTick = 0;
                            }
                            else
                            {
                                UpdateDiamondGrid();

                                if (!m_bIsUpdatingGrid)
                                {
                                    if (!IsAvailableMove())
                                    {
                                        m_gameState = GameState.gsGameOver;
                                        if (IsAllGridClean())
                                            SoundManager.Instance().PlaySound("victorySound");
                                        else
                                            SoundManager.Instance().PlaySound("gameoverSound");
                                    }
                                } 
                            }

                            TextBlock txtScore = (TextBlock)m_gameScene.SceneCanvas.FindName("txtScore");
                            txtScore.Text = "Score : " + Convert.ToString(m_gameScore);
                        }
                        catch (Exception e)
                        {
                            TextBlock txtScore = (TextBlock)m_gameScene.SceneCanvas.FindName("txtScore");
                            txtScore.Text = "Exception: " + e.Message;
                        }

                        break;
                    }

                case GameState.gsGameOver:
                    {
                        if (IsAllGridClean())
                        {                           
                            if (!m_gameScene.ExistObject(m_finishSprite))
                                m_gameScene.AddObject(m_finishSprite);                            
                        }
                        else
                        {                            
                            if (!m_gameScene.ExistObject(m_gameOverSprite))
                                m_gameScene.AddObject(m_gameOverSprite);                            
                        }
                        break;
                    }

                case GameState.gsEnd:
                    {
                        RemoveSpriteObjectFromScene();
                        break;
                    }
            }

            // update explosions...
            int i = 0;
            while (i < m_explosionList.Count)
            {
                if (!m_explosionList[i].Active)
                {
                    m_explosionList.RemoveAt(i);
                    continue;
                }
                m_explosionList[i].Update(pTick);
                i++;
            }
            
            // update scene...
            m_gameScene.Update(pTick);
        }
    }
}
